package shoppingmall.common.fileUpload;

import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import shoppingmall.common.dto.Result;

import shoppingmall.common.transitionalInformation.Message;

import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.text.SimpleDateFormat;
import java.util.*;

@SuppressWarnings({"all"})
@Slf4j
@RestController
public class FileUpload {
    /**
     * 公共服务-文件上传控制相关类
     */
    //设置文件保存在本地的磁盘位置
    //private String saveFilePath = "D:\\Program Files\\IntelliJ IDEA 2023\\WorkSpace\\ShoppingMall\\src\\main\\resources\\static\\upload\\";  //项目static目录下
    private String saveFilePath = System.getProperty("user.dir").toString() + "\\upload\\";  //项目根路径+上传的文件夹(本地开发模式)
    //private String saveFilePath = System.getProperty("user.dir").toString() + "//upload//";  //项目根路径+上传的文件夹(打包成jar)

    /**
     * @统一说明：本控制层类上传文件的方式有两种,请求方式(POST)
     * 1.方式一:
     *  请求地址:"/uploadByBlob"
     *  二进制blob传输,前端采用formData对象与本接口传输文件
     *  需要设置请求头: headers:{"Content-Type":"multipart/form-data"}
     *  需要在构建formData对象是指定文件名为'multipartFile':formData.append("multipartFile",selectedFile);  //将选择的文件对象添加进去 append("文件名",文件对象实例),可以追加多个
     *  支持多个文件上传
     *
     * 2.方式二:
     *  请求地址:"/uploadByBase64"
     *  文件转为base64编码后进行传输
     *  注意base64需要安全编码:例'data:text/plain;base64,aGVsbG8sd29yZCzkvaDlpb0=',此种格式需将","之前的字符截掉，只传后面的字符
     *  注意post的body参数为 data = {base64FileList:[{fileName:"",fileBase64:""},...] }
     *  支持多个文件上传
     *
     *
     *  -----以上两种方式未进行支持 切片上传 和 断点续传,后续将进行适配支持;目前spring boot框架配置的是单文件单次不超过10MB,多文件单次不超过100MB
     *  2024.1.29
     */

    //文件上传方法1
    //@CrossOrigin(origins = "*", methods = {RequestMethod.GET, RequestMethod.POST}, allowedHeaders = "*")
    @PostMapping("/uploadByBlob")
    public Result handleFileUpload(@RequestParam(value = "multipartFile",required = true) MultipartFile[] multipartFiles) {

        //定义数组，存储接收到的multipartFiles文件数组写入本地或云端的地址信息，一并返回给前端
        ArrayList<String> filesPath = new ArrayList<>();

        //处理 MultipartFile[] 类型的文件数组:增强for循环
        for(MultipartFile multipartFile : multipartFiles) {
            if (multipartFile != null && !multipartFile.isEmpty()) {
                try {
                    // 保存 MultipartFile 到服务器的某个路径(例：http://127.0.0.1:8090/xxx/yyy/zzz.zip)
                    //处理得到文件类型
                    String fileType = multipartFile.getOriginalFilename().substring(multipartFile.getOriginalFilename().lastIndexOf("."));
                    //创建保存文件的子目录(如upload/20240416)
                    String folder = createFolder();  //返回子目录名称(存在则不创建)
                    //文件的全路径
                    Path path = Paths.get(saveFilePath + folder + "\\" + createFileNameByTime() + fileType);  //文件名: 服务器地址 + 子目录 + 时间戳编码 + 文件类型
                    //Path path = Paths.get(saveFilePath + folder + "//" + createFileNameByTime() + fileType);
                    //目前写入在服务器的磁盘，后续考虑上传到阿里云oos
                    multipartFile.transferTo(path);

                    //收集文件写入的地址信息
                    //filesPath.add(path.toString());
                    String delPath = path.toString().substring(path.toString().lastIndexOf("\\") + 1);  //对路径进行处理
                    //String delPath = path.toString().substring(path.toString().lastIndexOf("/") + 1);  //对路径进行处理(jar)
                    //String serverAddress = "http://127.0.0.1:8090/upload/" + folder + "/";  //加了此句会抛异常:发生异常：No static resource 5486...
                    String serverAddress = Message.getServerAddress() + "/upload/" + folder + "/";  //从配置文件中获取服务器地址信息 + ...
                    filesPath.add(serverAddress + delPath);

                    log.info("接收到blob文件且已写入:{}", multipartFile.getOriginalFilename());
                } catch (IOException e) {
                    e.printStackTrace();
                    return Result.error("MultipartFile类型文件上传失败,请检查是否设置了请求头为'ContentType':'multipart/form-data',以及formData对象文件名是否为:'multipartFile'");
                }
            }
        }
        //返回信息给前端,并将文件在服务器上的地址一并返回  fileAddress
        //return Result.success("文件上传成功");
        return Result.success(filesPath);
    }

    //文件上传方法2
    @PostMapping("/uploadByBase64")
    public Result handleFileUpload(@RequestBody(required = true) Base64Files base64Files){

        //定义一个List数组，存放接收到的Base64文件写入到服务器磁盘或云端的地址，并返回给前端
        List<String> filesPath = new ArrayList<>();

        //获取base64Files对象的迭代器
        Iterator<Base64File> iterator = base64Files.getBase64FileList().iterator();
        while (iterator.hasNext()){

            Base64File base64File = iterator.next();

            // 处理 Base64 编码的文件
            String base64_File = base64File.getFileBase64();  //bse64编码字符
            String base64_file_name = base64File.getFileName();  //base64文件名
            String base64_file_type = base64_file_name.substring(base64_file_name.lastIndexOf("."));  //通过处理文件名得到文件类型

            if (base64_File != null && !base64_File.trim().isEmpty()) {
                try {
                    // 解码 Base64 编码的文件并保存到服务器的某个路径
                    byte[] fileBytes = Base64.getDecoder().decode(base64_File);

                    // 读取字节数组的前8个字节，作为文件的魔数
                    byte[] magicNumber = new byte[8];
                    System.arraycopy(fileBytes, 0, magicNumber, 0, 8);
                    // 根据魔数判断文件类型
                    String fileType = getFileType(magicNumber);  //暂时用不上

                    //目前写入在服务器的磁盘，后续考虑上传到阿里云oos
                    //创建保存文件的子目录(如upload/20240416)
                    String folder = createFolder();  //返回子目录名称(存在则不创建)
                    //文件全路径
                    Path path = Paths.get(saveFilePath +folder + "\\" + getStringByUuid() + base64_file_type);  //本地开发模式
                    //Path path = Paths.get(saveFilePath +folder + "//" + getStringByUuid() + base64_file_type);  //jar使用模式
                    Files.write(path, fileBytes);

                    //收集文件写入的地址信息
                    String delPath = path.toString().substring(path.toString().lastIndexOf("\\") + 1);  //对路径进行处理
                    String serverAddress = "http://127.0.0.1:8090/upload/" + folder + "/";  //加了此句会抛异常:发生异常：No static resource 5486...
                    //应该从配置文件获取服务器地址信息,此处暂不加...
                    filesPath.add(serverAddress + delPath);

                    log.info("接收到base64文件且已写入:{}",base64_file_name);

                } catch (IOException e) {
                    e.printStackTrace();
                    return Result.error("Base64文件上传失败,检查上传的Base64字符是否为安全字符!");
                }
            }
        }

        //返回信息给前端,并将文件在服务器上的地址一并返回
        //return Result.success("文件上传成功");
        return Result.success(filesPath);
    }


    /*相关处理方法*/
    //根据文件魔数判断其文件类型
    private String getFileType(byte[] magicNumber) {
        // 根据魔数判断文件类型
        if (magicNumber[0] == (byte) 0xD0 && magicNumber[1] == (byte) 0xCF && magicNumber[2] == (byte) 0x11 && magicNumber[3] == (byte) 0xE0 && magicNumber[4] == (byte) 0xA1 && magicNumber[5] == (byte) 0xB1 && magicNumber[6] == (byte) 0x1A && magicNumber[7] == (byte) 0xE1) {
            return "docx";
        } else if (magicNumber[0] == (byte) 0x00 && magicNumber[1] == (byte) 0x6E && magicNumber[2] == (byte) 0x1E && magicNumber[3] == (byte) 0xF0) {
            return "pptx";
        } else if (magicNumber[0] == (byte) 0x00 && magicNumber[1] == (byte) 0x6E && magicNumber[2] == (byte) 0x1E && magicNumber[3] == (byte) 0xF0) {
            return "xlsx";
        } else if (magicNumber[0] == (byte) 0x25 && magicNumber[1] == (byte) 0x50 && magicNumber[2] == (byte) 0x44 && magicNumber[3] == (byte) 0x46) {
            return "pdf";
        } else if (magicNumber[0] == (byte) 0x50 && magicNumber[1] == (byte) 0x4B && magicNumber[2] == (byte) 0x03 && magicNumber[3] == (byte) 0x04) {
            return "zip";
        } else if (magicNumber[0] == (byte) 0xFF && magicNumber[1] == (byte) 0xD8) {
            return "jpeg";
        } else if (magicNumber[0] == (byte) 0x89 && magicNumber[1] == (byte) 0x50 && magicNumber[2] == (byte) 0x4E && magicNumber[3] == (byte) 0x47) {
            return "png";
        } else if (magicNumber[0] == (byte) 0x47 && magicNumber[1] == (byte) 0x49 && magicNumber[2] == (byte) 0x46 && magicNumber[3] == (byte) 0x38) {
            return "gif";
        } else {
            return "unknown";
        }
    }

    //处理文件命名方式1,采用UUID解决--->uuid.jpg/uuid.zip
    private String getStringByUuid () {
        //return UUID.randomUUID().toString();  //10d150ec-ae48-414f-b6f2-38b3e7940645
        return UUID.randomUUID().toString().replaceAll("-","");
    }

    //处理文件命名方式2,采用时间戳加随机数方式解决
    private String createFileNameByTime () {
        Date today = new Date();
        String fileName = today.getTime() + Integer.toString((int)(100 + Math.random() * (9999 - 1000 + 1)));  //文件名称 = 时间戳 + 随机四位数
        return fileName;
    }

    //创建目录存储文件(如在upload目录下创建20240616文件夹)
    private String createFolder () {
        Calendar calendar = Calendar.getInstance();
        String year = String.valueOf(calendar.get(Calendar.YEAR));
        String month = ((calendar.get(Calendar.MONTH)) + 1) < 10 ? "0" + ((calendar.get(Calendar.MONTH)) + 1) : Integer.toString((calendar.get(Calendar.MONTH)) + 1);
        String date = ((calendar.get(Calendar.DATE))) < 10 ? "0" + ((calendar.get(Calendar.DATE))) : String.valueOf((calendar.get(Calendar.DATE)));
        String folder = year + month + date;   //文件夹名称:20240416
        // 指定要创建的目录路径(项目根目录下\\upload\\xxxxxxxx)
        String directoryPath = saveFilePath + folder;

        // 创建一个File对象，表示要创建的目录
        File newDirectory = new File(directoryPath);

        // 使用exists()方法检查目录是否已经存在
        if (newDirectory.exists()) {
            //System.out.println("目录已经存在");
        } else {
            // 使用mkdir()方法创建目录
            boolean isCreated = newDirectory.mkdir();

            // 检查目录是否创建成功
            if (isCreated) {
                //System.out.println("目录创建成功");
            } else {
                //System.out.println("目录创建失败");
            }
        }
        return folder;
    }

}
//Base64文件实例对象集合
class Base64Files {
    private List<Base64File> base64FileList;

    public List<Base64File> getBase64FileList() {
        return base64FileList;
    }

    public void setBase64FileList(List<Base64File> base64FileList) {
        this.base64FileList = base64FileList;
    }
}

//Base64文件实例对象
class Base64File {
    private String fileBase64;
    private String fileName;

    public String getFileBase64() {
        return fileBase64;
    }

    public void setFileBase64(String fileBase64) {
        this.fileBase64 = fileBase64;
    }

    public String getFileName() {
        return fileName;
    }

    public void setFileName(String fileName) {
        this.fileName = fileName;
    }
}

